﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Viewer3D
{
    class ViewPoint
    {
        Point3 X;
        Point2 x;
        Color color;

        public ViewPoint() { }
        public ViewPoint(Point3 pt)
        {
            X = pt;
        }
        public ViewPoint(Point3 pt, Color color)
        {
            X = pt;
            this.color = color;
        }
        public Point3 WCS
        {
            set { X = value; }
            get { return X; }
        }

        public Point2 ICS
        {
            set { x = value; }
            get { return x; }
        }

        public Color Color
        {
            set { Color = value; }
            get { return color; }
        }
    }
    struct StlFacet
    {
        Point3 vertex0;
        Point3 vertex1;
        Point3 vertex2;
        Point3 normal;

        public Point3 Vertex0
        {
            set { vertex0 = value; }
            get { return vertex0; }
        }

        public Point3 Vertex1
        {
            set { vertex1 = value; }
            get { return vertex1; }
        }

        public Point3 Vertex2
        {
            set { vertex2 = value; }
            get { return vertex2; }
        }

        public Point3 Normal
        {
            set { normal = value; }
            get { return normal; }
        }
    }

    class StlModel
    {
        string name;
        List<StlFacet> facets;

        public enum SaveType { ASCII,BINARY }

        public StlModel()
        {
            facets = new List<StlFacet>();
            name = "model";
        }

        public StlModel(string stlPath)
        {
            facets = new List<StlFacet>();
            ReadFromSTL(stlPath);
        }

        public List<StlFacet> Facets
        {
            get { return facets; }
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public void Save(string fileName, SaveType type = SaveType.ASCII)
        {
            FileStream fsFile = new FileStream(fileName, FileMode.Create);
            if (type == SaveType.ASCII)
            {
                StreamWriter sw = new StreamWriter(fsFile);
                //write head
                sw.WriteLine("solid " + name);
                foreach (var item in facets)
                {
                    // write normal
                    sw.Write("\tfacet normal ");
                    sw.Write(string.Format("{0:e}", item.Normal.X) + " ");
                    sw.Write(string.Format("{0:e}", item.Normal.Y) + " ");
                    sw.WriteLine(string.Format("{0:e}", item.Normal.Z));
                    // write vertex
                    sw.WriteLine("\t\touter loop");
                    // vertex 0
                    sw.Write("\t\t\tvertex ");
                    sw.Write(string.Format("{0:e}", item.Vertex0.X) + " ");
                    sw.Write(string.Format("{0:e}", item.Vertex0.Y) + " ");
                    sw.WriteLine(string.Format("{0:e}", item.Vertex0.Z));
                    // vertex 1
                    sw.Write("\t\t\tvertex ");
                    sw.Write(string.Format("{0:e}", item.Vertex1.X) + " ");
                    sw.Write(string.Format("{0:e}", item.Vertex1.Y) + " ");
                    sw.WriteLine(string.Format("{0:e}", item.Vertex1.Z));
                    // vertex 2
                    sw.Write("\t\t\tvertex ");
                    sw.Write(string.Format("{0:e}", item.Vertex2.X) + " ");
                    sw.Write(string.Format("{0:e}", item.Vertex2.Y) + " ");
                    sw.WriteLine(string.Format("{0:e}", item.Vertex2.Z));
                    // end loop
                    sw.WriteLine("\t\tendloop");
                    // end facet
                    sw.WriteLine("\tendfacet");
                }
                sw.WriteLine("endsolid " + name);
                sw.Close();
                fsFile.Close();
            }
            else
            {
                BinaryWriter bw = new BinaryWriter(fsFile);
                //write head : 80 bytes UINT8[80]
                byte[] bytes = Encoding.ASCII.GetBytes(name);
                bw.Write(bytes);
                for (int i = name.Length; i<80; i++)
                {
                    bw.Write(' ');
                }
                //write number of traianles : 4 bytes UINT32
                bw.Write(Convert.ToUInt32(facets.Count));

                foreach (var item in facets)
                {
                    // write normal : 4*3 bytes REAL32[3]
                    bw.Write((float)item.Normal.X);
                    bw.Write((float)item.Normal.Y);
                    bw.Write((float)item.Normal.Z);
                    // write vertex 
                    // vertex 0 : 4*3 byte REAL32[3]
                    bw.Write((float)item.Vertex0.X);
                    bw.Write((float)item.Vertex0.Y);
                    bw.Write((float)item.Vertex0.Z);
                    // vertex 1 : 4*3 byte REAL32[3]
                    bw.Write((float)item.Vertex1.X);
                    bw.Write((float)item.Vertex1.Y);
                    bw.Write((float)item.Vertex1.Z);
                    // vertex 2 : 4*3 byte REAL32[3]
                    bw.Write((float)item.Vertex2.X);
                    bw.Write((float)item.Vertex2.Y);
                    bw.Write((float)item.Vertex2.Z);
                    // write attribute : 3 bytes UINT16
                    bw.Write(Convert.ToUInt16(0));
                }
                bw.Close();
                fsFile.Close();
            }
        }        

        public void ReadFromSTL(string fileName)
        {
            if ( facets == null)
                facets = new List<StlFacet>();
            FileInfo fileinfo = new FileInfo(fileName);
            if (fileinfo.Extension != ".stl" || fileinfo.Length == 0)
            {
                MessageBox.Show("文件错误！");
                return;
            }
            else
            {
                if (Auxiliary.IsBinary(fileName))
                    ReadBinarySTL(fileinfo);
                else
                    ReadAsciiSTL(fileinfo);
            }
        }

        private void ReadAsciiSTL(FileInfo fileinfo)
        {
            FileStream fs = fileinfo.OpenRead();
            StreamReader sr = new StreamReader(fs);
            string line;
            char[] sep = new char[1] { ' ' };
            double x, y, z;

            // read head
            line = sr.ReadLine();
            line = line.Trim();
            string[] token = line.Split(sep);
            name = token.Last();

            while (true)
            {
                // read normal
                line = sr.ReadLine();
                line = line.Trim();
                token = line.Split(sep);
                if (token[0] == "endsolid")
                    break;
                x = Convert.ToDouble(token[2]);
                y = Convert.ToDouble(token[3]);
                z = Convert.ToDouble(token[4]);
                StlFacet facet = new StlFacet();
                facet.Normal = new Point3(x, y, z);
                // outer loop
                sr.ReadLine();
                // vertex 0
                line = sr.ReadLine();
                line = line.Trim();
                token = line.Split(sep);
                x = Convert.ToDouble(token[1]);
                y = Convert.ToDouble(token[2]);
                z = Convert.ToDouble(token[3]);
                facet.Vertex0 = new Point3(x, y, z);
                // vertex 1
                line = sr.ReadLine();
                line = line.Trim();
                token = line.Split(sep);
                x = Convert.ToDouble(token[1]);
                y = Convert.ToDouble(token[2]);
                z = Convert.ToDouble(token[3]);
                facet.Vertex1 = new Point3(x, y, z);
                // vertex 2
                line = sr.ReadLine();
                line = line.Trim();
                token = line.Split(sep);
                x = Convert.ToDouble(token[1]);
                y = Convert.ToDouble(token[2]);
                z = Convert.ToDouble(token[3]);
                facet.Vertex2 = new Point3(x, y, z);
                // endloop
                sr.ReadLine();
                // endfacet
                sr.ReadLine();
                // append
                facets.Add(facet);
            }
            sr.Close();
            fs.Close();
        }

        private void ReadBinarySTL(FileInfo fileinfo)
        {
            FileStream fs = fileinfo.OpenRead();
            BinaryReader br = new BinaryReader(fs);
            byte[] buff = new byte[80];

            //read name
            buff = br.ReadBytes(80);
            name = Encoding.ASCII.GetString(buff).Trim();
            //read number of triangles
            uint length = br.ReadUInt32();
            // read facets
            float x, y, z;
            for (var i=0; i<length; i++)
            {
                StlFacet facet = new StlFacet();
                // read normal
                x = br.ReadSingle();
                y = br.ReadSingle();
                z = br.ReadSingle();
                facet.Normal = new Point3(x, y, z);
                // read vertex 0
                x = br.ReadSingle();
                y = br.ReadSingle();
                z = br.ReadSingle();
                facet.Vertex0 = new Point3(x, y, z);
                // read vertex 1
                x = br.ReadSingle();
                y = br.ReadSingle();
                z = br.ReadSingle();
                facet.Vertex1 = new Point3(x, y, z);
                // read vertex 2
                x = br.ReadSingle();
                y = br.ReadSingle();
                z = br.ReadSingle();
                facet.Vertex2 = new Point3(x, y, z);
                // read Attribute by count
                br.ReadUInt16();
                // add to list
                facets.Add(facet);
            }
            br.Close();
            fs.Close();
        }
    }    
}